1 Introduction / Setup

This workshop/tutorial will walk you through the basics of using the Leaflet mapping package in R. If you prefer to see all of the code at once, with no figures displayed, please refer to the mapping-in-R-RAW.html file. You can also access the slides that accompany this talk by using this link. (hint: press ‘w’ when the slides open to view them in widescreen)

To begin, lets set up our packages and environment.

1.1 Installing packages

To install Leaflet, use the following command install.packages("leaflet"). We will be using other packages in this tutorial. If you don’t already have any of the following packages installed, you can use this code to do so:

install.packages("leaflet")
install.packages("RColorBrewer")
install.packages("rgdal")
install.packages("raster")
install.packages("ggplot2")
install.packages("ggmap")
install.packages("magrittr")

1.2 Loading packages

You only install a package once, but each time you want to use it, you have to load it with library()

library(leaflet)
library(RColorBrewer)
library(rgdal)
library(raster)
library(ggplot2)
library(ggmap)
library(magrittr)
library(gstat)
library(maptools)

1.3 Set working directory

1.4 Loading crime data

Load some sample crime data from the City of Berkeley Crime Incidence Data Portal.

###Load and clean data
source("loadData.R")

Now that we’ve loaded the crime data, lets take a look at the first few lines of our new variable titled berkeleyCrime:

berkeleyCrime[1:5,c("OFFENSE","EVENTDT","EVENTTM","lat","long")]
##                    OFFENSE    EVENTDT EVENTTM      lat      long
## 1 THEFT MISD. (UNDER $950) 01/30/2016   16:30 37.87123 -122.2684
## 2 THEFT MISD. (UNDER $950) 01/11/2016   10:57 37.86865 -122.2592
## 3                VANDALISM 09/05/2015   13:55 37.85028 -122.2727
## 4    ASSAULT/BATTERY MISD. 12/05/2015   00:00 37.86774 -122.2508
## 5        DOMESTIC VIOLENCE 11/12/2015   12:20 37.86689 -122.2951

2 Mapping with GGPLOT

2.1 GGPLOT map

    map <- ggplot() + 
           geom_point(data=berkeleyCrime, aes(x=long, y=lat))
    map

2.2 Add a background

We can use the function get_map() to pull a background map from google, OpenStreetMap (OSM) or Stamen.

    background <- get_map(location=c(lon = mean(berkeleyCrime$long),
                                     lat = mean(berkeleyCrime$lat)),
                          zoom=14,
                          maptype = "terrain",
                          source="google",
                          color="bw")

    map <- ggmap(background) + coord_equal() + 
      geom_point(data=berkeleyCrime, aes(x=long, y=lat, alpha=0.3,
                                         size=7, color=CVLEGEND)) +
      scale_size_continuous(range = c(3), guide=FALSE) +
      scale_alpha_continuous(range = c(.3), guide=FALSE)
    
    map

Some things you can try to change this map:

  • Change maptype from "terrain" to "satellite"
  • Change color from “bw” to “color”.
  • In the console, type ?get_map to view other options to customize your background map

3 Leaflet Map: Crime Map

3.1 Intro to Leaflet

Leaflet is one of the most popular open-source JavaScript libraries for interactive maps. We can easily enable Leaflet and pull up the standard OpenStreetMap (OSM) baselayer using the following command:

leaflet() %>% addTiles() %>% addMarkers(lng = -122.258, lat = 37.870)

Try panning around and zooming in on this map, and notice how more and more data is loaded.

3.2 Piping %>%

So what’s with the %>% used in the previous example??

Throughout this presentation, I’ll use something called a pipe which looks like this: %>% Pipes are a way of passing the result of one function into the next function. For example, the traditional way of pulling up a map with leaflet, adding tiles and then setting the view would be as follows:

addMarkers(addTiles(leaflet()), lng = -122.258, lat = 37.870).

Piping lets us flip the notation inside out, so it reads more naturally:

leaflet() %>% addTiles() %>% addMarkers(lng = -122.258, lat = 37.870).

See how the code in this second example reads like a recipe of what we want to do:

  1. enable leaflet: leaflet() %>%
  2. add background tiles: addTiles() %>%
  3. set the view of the map: addMarkers(lng = -122.258, lat = 37.870)

You can read more about using pipes here and here.

3.3 Crime Map

Lets use out new knowledge of Leaflet to map the first 100 crimes in our berkeleyCrime dataset:

leaflet() %>% addTiles() %>% addMarkers(data=berkeleyCrime[1:100,])

3.4 Adding other layers

For more information, pull up the help on any of the add functions in leaflet (e.g. ?addMarkers):

addMarkers(map, lng = NULL, lat = NULL, layerId = NULL, group = NULL, icon = NULL,
           popup = NULL, options = markerOptions(), clusterOptions = NULL,
           clusterId = NULL, data = getMapData(map))

addCircleMarkers(map, lng = NULL, lat = NULL, radius = 10, layerId = NULL, 
    group = NULL, stroke = TRUE, color = "#03F", weight = 5, opacity = 0.5, 
    fill = TRUE, fillColor = color, ....)

addTiles(map, urlTemplate = "http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
         attribution = NULL, layerId = NULL, group = NULL, options = tileOptions())

addCircles(...)
addRasterImage(...)
addPolylines(...)
addRectangles(...)
addPolygons(...)
addPopups()
...

3.5 Popups

In this example, we change out markers to circleMarkers so we can adjust the color (and radius if we wish) and also add a popup that displays information when we click on the circle:

leaflet() %>% addTiles() %>%
      addCircleMarkers(data=berkeleyCrime[1:100,],
                       stroke = FALSE, fillOpacity = 0.5, radius=8,
                       popup=~paste("<strong>Offense:</strong>",CVLEGEND,
                       "<br>",
                       "<strong>Date:</strong>",EVENTDT,
                       "<br>",
                       "<strong>Time:</strong>",EVENTTM))

3.6 Raster Layers

#subset data
larceny <- subset(berkeleyCrime,berkeleyCrime$CVLEGEND=="LARCENY")

#make raster
sSp <- as(SpatialPoints(larceny[,c("long","lat")]), "ppp")  # convert points to pp class
Dens <- density(sSp, adjust = 0.3)  # create density object
r <- raster(Dens)
crs(r) <- sp::CRS("+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs")

pal.act <- colorNumeric(c("#ffffd400","#fed98e","#fe9929","#d95f0e","#993404"), values(r), na.color = "transparent", alpha=TRUE)

leaflet() %>% addProviderTiles("CartoDB.Positron") %>%
  addCircleMarkers(data=larceny[1:1000,], radius=2, fillColor="red", color=F) %>%
  addRasterImage(r, opacity=0.7, colors=pal.act)

3.7 Color Coding by Crime

    offenseColor <- colorFactor(rainbow(25), berkeleyCrime$CVLEGEND)
    
    leaflet(berkeleyCrime[1:100,]) %>%
      addProviderTiles("CartoDB.Positron") %>%
      addCircleMarkers(
        stroke = FALSE, fillOpacity = 0.5, radius=6,
        color = ~offenseColor(CVLEGEND),
        popup = ~paste("<strong>Offense:</strong>",CVLEGEND,
                       "<br>",
                       "<strong>Date:</strong>",EVENTDT,
                       "<br>",
                       "<strong>Time:</strong>",EVENTTM)
      ) %>%
      addLegend(title = "Type of Offense", pal = offenseColor,
                values = ~CVLEGEND, opacity = 1, position="bottomleft")

3.8 Clustering

If we want to view many crimes, say all 4,598 crimes in this dataset, we may want to cluster our results. Displaying that many points at once will slow down our computer, and it just hard to visually process. Do cluster results in leaflet, we use the clusterOptions variable with markers or circleMarkers.

leaflet(berkeleyCrime) %>%
      addProviderTiles("CartoDB.Positron") %>%
      addCircleMarkers(
        stroke = FALSE, fillOpacity = 0.5, radius=6, color = ~offenseColor(CVLEGEND),
        clusterOptions = markerClusterOptions(spiderfyDistanceMultiplier=2, maxClusterRadius=60),
        popup = ~paste("<strong>Offense:</strong>",CVLEGEND,
                       "<br>",
                       "<strong>Date:</strong>",EVENTDT,
                       "<br>",
                       "<strong>Time:</strong>",EVENTTM)
      ) %>%
      addLegend(title = "Type of Offense", pal = offenseColor, values = ~CVLEGEND, opacity = 1, position="bottomleft")

4 Map #2: Yosemite Peaks

For this example, we’ll use OpenStreetMap (OSM) data on the peaks in Yosemite National Park. This OSM data is extracted from TurboOverpass. Come to a later workshop to learn how to do this!

4.1 Adding Polygons

Loading a shapefile of Yosemite National Park using the function readOGR(). Notice the different notation where the variable yosemite is loaded directly into the leaflet() function. If you’re only loading one layer into leaflet this is easier, but if you’re loading multiple layers, it’s best to be explicit and specify your data layer in the function that adds that layer (i.e. addPolygons(), addMarkers(), etc.)

#load data
yosemite <- readOGR("./data/yosemite", "yosemite", verbose = FALSE) #yosemite boundary shapefile
PCT <- readOGR("./data/PCT.geojson", "OGRGeoJSON", verbose=FALSE)

#make map
leaflet() %>% addTiles() %>% addPolylines(data = yosemite) %>% addPolylines(data = PCT, color="red")

4.2 Mapping peaks

#load peaks points and PCT line
peaks <- readOGR("./data/peaks.geojson", "OGRGeoJSON", verbose=FALSE)

#convert peak values to numeric
peaks <- subset(peaks, peaks@data$ele!="0")
peaks@data$ele <- round(as.numeric(levels(peaks@data$ele))[peaks@data$ele] * 3.28084)

#make map of peaks
leaflet() %>%
  addTiles() %>%
  addCircleMarkers(data=peaks, weight=0, radius=8, fillOpacity=0.6,
                   popup=~paste("Name: ",name,"<br>Elevation: ",ele,"feet")) %>%
  addPolylines(data=yosemite, fillOpacity = 0, dashArray=c(10,10), fill=FALSE, color="red") %>%
  addPolylines(data=PCT, fillOpacity = 0, dashArray=c(10,10), fill=FALSE, color="red")

Similarly to how we colored crimes by type of crime, we can size these circles based on their elevation using the code: radius=~ele/1000.

I’ve also added a marker using an icon to the subset of all peaks that are over 10,000 feet. To do this, we first set the icon setting using the function icons() and then usingaddMarkers with the subset of peaks data=subset(peaks, peaks@data$ele>10000).

#icon settings
peakIcons <- icons(
  iconUrl = "./data/Mountain-512.png",
  iconWidth = 15, iconHeight = 15,
  iconAnchorX = 7.5, iconAnchorY = 8.5
  )

leaflet() %>%
  addTiles() %>%
  addCircleMarkers(data=peaks, weight=0, radius=~ele/1000, fillOpacity=0.7, 
                   popup=~paste("Name: ",name,"<br>Elevation: ",ele)) %>%
  addMarkers(data=subset(peaks, peaks@data$ele>10000),
             icon = peakIcons,
             popup=~paste("Name: ",name,"<br>Elevation: ",ele)) %>%
  addPolylines(data=yosemite, fillOpacity = 0, dashArray=c(10,10), fill=FALSE, color="red")

4.3 Legends

Lets add some color to our circles. Do do this, we create a palette, just like we did for Berkeley crimes. In this example, there is an extra line where I first define the palette spectral so I can flip the color scheme. Then we map elevations to the palette in the line that begins eleColor.

To add a legend to the map, we use the function addLegend() and assign the pal to the mapped palette eleColor.

#create color scale from elevation
spectral <- brewer.pal(11, "Spectral")  %>% rev()
eleColor <- colorBin(spectral, peaks@data$ele)

leaflet(peaks) %>%
  addTiles() %>%
  addCircleMarkers(weight=0, radius=~ele/1000, fillOpacity=0.7, color = ~eleColor(ele),
                   popup=~paste("Name: ",name,"<br>Elevation: ",ele)) %>%
  addMarkers(data=subset(peaks,peaks@data$ele>10000),
             icon = peakIcons,
             popup=~paste("Name: ",name,"<br>Elevation: ", ele)) %>%
    addLegend(title = "Peak Elevation", pal = eleColor,
            values = ~ele, opacity = 1, position="bottomleft") %>%
  addPolylines(data=yosemite, fillOpacity = 0, dashArray=c(10,10), fill=FALSE, color="blue")

4.4 Basemaps

You can use the function addProviderTiles() to change the background map, sometimes called the ‘basemap’. To view the options for background maps, visit http://leaflet-extras.github.io/leaflet-providers/preview/.

So we don’t have to retype our code every time, lets save our map (without a basemap) as a variable:

peakMap <- leaflet() %>%
  addCircleMarkers(data=peaks, weight=0, radius=~ele/1000, fillOpacity=0.7, color = ~eleColor(ele),
                   popup=~paste("Name: ",name,"<br>Elevation: ",ele)) %>%
  addLegend(title = "Peak Elevation", pal = eleColor,
            values = peaks@data$ele, opacity = 1, position="bottomleft") %>%
  addMarkers(data=subset(peaks,peaks@data$ele>10000),
             icon = peakIcons,
             popup=~paste("Name: ",name,"<br>Elevation: ",ele)) %>%
  addPolylines(data=yosemite, fillOpacity = 0, dashArray=c(10,10), fill=FALSE, color="white")

Great, now we can call peakMap and just add in different types of basemaps using addProviderTiles

4.4.1 Satellite Imagery

peakMap %>% addProviderTiles("Esri.WorldImagery")

4.4.2 Positron

peakMap %>% addProviderTiles("CartoDB.Positron") %>%
  addPolylines(data=yosemite, fillOpacity = 0, dashArray=c(10,10), fill=FALSE, color="black")

4.4.3 Mapbox

mapbox <- "http://api.tiles.mapbox.com/v4/mapbox.outdoors/{z}/{x}/{y}.png?access_token=pk.eyJ1Ijoiam9zaHBlcHBlciIsImEiOiJuTWdrY2k4In0.HCCXtgU04scrTB_-ON4kjA"

peakMap %>% addTiles(urlTemplate = mapbox)

4.4.4 Weather Maps

You can even pull in weather data. In this exmaple, I’ve overlaid weather data on top of the CartoDB.Positron basemap.

leaflet() %>% addProviderTiles("CartoDB.Positron") %>%
  addProviderTiles("OpenWeatherMap.Temperature", options=providerTileOptions(opacity=0.2)) %>%
  setView(lng = -122.2579335, lat = 37.8700441, zoom=4)

4.5 Layer Switcher

To add ‘controls’ to our map, we use the function addLayersControl(). We assign a group to each basemap and layer (polygon, marker, line, etc). Then layer switcher allows us to toggle between different basemaps and turn on/off each of the layers.

leaflet() %>%
  addCircleMarkers(data=peaks, weight=0, radius=~ele/1000, fillOpacity=0.7, color = ~eleColor(ele),
                   popup=~paste("Name: ",name,"<br>Elevation: ",ele),
                   group="Peaks") %>%
  addLegend(title = "Peak Elevation", pal = eleColor,
            values = peaks@data$ele, opacity = 1, position="bottomleft") %>%
  addMarkers(data=subset(peaks,peaks@data$ele>10000),
             icon = peakIcons, popup=~paste("Name: ",name,"<br>Elevation: ",ele),
             group="Mountain Icons") %>%
  addPolylines(data=yosemite, fillOpacity = 0, dashArray=c(10,10),
               fill=FALSE, color="#3288BD", group="Yosemite") %>%
  addPolylines(data=PCT, dashArray=c(10,10),
               fill=FALSE, color="#9E0142", group="PCT") %>%
  #add basemaps
  addProviderTiles("CartoDB.Positron", group="Simple") %>%
  addProviderTiles("Esri.WorldImagery", group="Satellite") %>%
  addTiles(urlTemplate = mapbox, group="Outdoors") %>%
  #Add layer switcher
   addLayersControl(
    baseGroups = c("Simple", "Satellite", "Outdoors"),
    overlayGroups = c("Peaks","Mountain Icons","Yosemite","PCT"),
    options = layersControlOptions(collapsed = FALSE)
  )